package ufrj.safcp.modules.coleta;

import java.awt.Dimension;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ufrj.safcp.controller.BaseController;
import ufrj.safcp.controller.State;
import ufrj.safcp.model.util.IntegerOccourrence;
import ufrj.safcp.tools.fingerprint.GriauleFingerprintHandler;
import ufrj.safcp.tools.fingerprint.IFingerprintHandler;
import ufrj.safcp.tools.fingerprint.IFingerprintReceiver;
import ufrj.safcp.tools.imagecapturing.IImageCapturing;
import ufrj.safcp.tools.imagecapturing.LTICivilImageCapturing;
import ufrj.safcp.tools.imageio.ImageDiskManipulator;
import ufrj.safcp.tools.imageprocessing.ICardImageProcessor;
import ufrj.safcp.tools.imageprocessing.MatlabCardImageProcessor;
import ufrj.safcp.tools.imageprocessing.UnredableNumberException;
import ufrj.safcp.tools.printing.IPrinter;
import ufrj.safcp.tools.printing.SimpleJavaPrinter;
import ufrj.safcp.tools.qrcode.IQRCodeHandler;
import ufrj.safcp.tools.qrcode.ZXingQRCodeHandler;
import ufrj.safcp.util.ConversionUtil;


public class ColetaController extends BaseController {
	
	private static final int THRESHOLD_OCORRENCIAS = 5;

	static {
		new Thread(new Runnable() {
			public void run() {
				// fazendo isso apenas para mostrar o splash da griaule no início.
				GriauleFingerprintHandler.getInstance();
			}
		}, "Iniciador Griaule").start();
	}
	
	private static Logger logger = LoggerFactory.getLogger(ColetaController.class);
	
	private static final int SEGUNDOS_TENTANDO_LER_CARTAO = 30;

	private ICardImageProcessor imageProcessor;
	
	private IFingerprintHandler fingerprintHandler;
	
	private IQRCodeHandler qrCodeHandler;
	
	private BufferedImage qrCodeImage;
	
	private IImageCapturing imageCapturing;
	
	private Integer cardNumber;
	
	private Integer fiscalNumber;

	public State AUTENTICANDO_FISCAL = new State(1, "Autenticando Fiscal") {

		@Override
		public void enterState() {
			fingerprintHandler = GriauleFingerprintHandler.getInstance();
			fingerprintHandler.init(25, new IFingerprintReceiver() {
				
				@Override
				public void receiveFingerprint(Image fingerprint, byte[] templateData, String templateString) {
					// TODO: verificar fiscal
					/*
					for (int i = 0; i < templatesFiscal.length; i++) {
						fingerprintHandler.match(templateData, templatesFiscal[i]);
					}
					*/
					fiscalNumber = 12345;
					getFrame().reportLeituraDigitalDoFiscal(1, true, fingerprint, "João da Silva de Oliveira Brito");
					try {
						Thread.sleep(2000);
					} catch (Exception e) {
						// do nothing
					}
					changeState(AUTENTICANDO_FISCAL, AGUARDANDO_CANDIDATO);
				}
			});
			fingerprintHandler.start();
			
		}

		@Override
		public void exitState() {
			//fingerprintHandler.stop();
			fingerprintHandler.destroy();
			fingerprintHandler = null;
		}
		
	};
	
	public State AGUARDANDO_CANDIDATO = new State(2, "Aguardando candidato") {

		public void enterState() {
			resetAllVariables();
		};
		

		public void exitState() {
			
		};
		
	};
	
	public State COLETANDO_NUMERO_INSCRICAO = new State(3, "Coletando número de inscrição") {
		public void enterState() {
			
			imageCapturing = LTICivilImageCapturing.getInstance();
			
			imageCapturing.init(0, new Dimension(320, 240));
			imageCapturing.start();
			imageCapturing.addImageReceiver(getFrame().getCameraPanel());
			
			imageProcessor = MatlabCardImageProcessor.getInstance();
			imageProcessor.initialize(ImageDiskManipulator.tempDir);
			
		};
		public void exitState() {
			imageCapturing.stop();
			imageCapturing = null;
			imageProcessor = null;
		};
	};
	
	public State COLETANDO_DIGITAIS = new State(4, "Coletando digitais") {
		
		int nCollectedTemplates;
		private byte[][] collectedTemplates;
		
		public void enterState() {
			
			nCollectedTemplates= 0;
			collectedTemplates = new byte[3][];
			
			fingerprintHandler = GriauleFingerprintHandler.getInstance();
			fingerprintHandler.init(25, new IFingerprintReceiver() {
				
				@Override
				public void receiveFingerprint(Image fingerprint, byte[] templateData, String templateString) {
					if (nCollectedTemplates == 0) { //pegando primeira digital
						
						fingerprintHandler.enroll(fingerprint, templateData);
						
						nCollectedTemplates++;
						collectedTemplates[0] = templateData;
						
						getFrame().reportLeituraDigitalDoCandidato(1, true, fingerprint);
					} else if (nCollectedTemplates == 1) {
							
							fingerprintHandler.enroll(fingerprint, templateData);
							
							nCollectedTemplates++;
							collectedTemplates[1] = templateData;
							
							getFrame().reportLeituraDigitalDoCandidato(2, true, fingerprint);
							
					} else if (nCollectedTemplates == 2) {
						boolean enrolledOk = fingerprintHandler.enroll(fingerprint, templateData);

						if (enrolledOk) {
							nCollectedTemplates++;
							collectedTemplates[2] = templateData;
							getFrame().reportLeituraDigitalDoCandidato(3, true, fingerprint);
							
							template = fingerprintHandler.getEnrolledTemplate();
							
							fingerprintHandler.stopEnroll();
							
							new Thread(new Runnable() {
								public void run() {
									try {
										Thread.sleep(100);
									} catch (InterruptedException e) {
									}
									changeState(COLETANDO_DIGITAIS, IMPRIMINDO_ETIQUETA);
								}
							}).start();
						} else {
							getFrame().reportLeituraDigitalDoCandidato(3, false, null);
						}
					}
					
				}
			});
		};
		
		public void exitState() {
			getFrame().reportLeituraDigitalDoCandidato(0, true, null);
			fingerprintHandler.destroy();
			fingerprintHandler = null;
		};
	};
	
	public State IMPRIMINDO_ETIQUETA = new State(5, "Imprimindo etiqueta") {

		public void enterState() {
			qrCodeHandler = ZXingQRCodeHandler.getInstance(250);
			
			byte[] info = new byte[template.length + 4 + 4]; // 4 para id do fiscal e 4 para inscrição do cadidato
			
			byte[] cardNumberBytes = ConversionUtil.convertIntToBytes(cardNumber);
			
			for (int i = 0; i < cardNumberBytes.length; i++) {
				info[i] = cardNumberBytes[i];
			}
			
			byte[] fiscalNumberBytes = ConversionUtil.convertIntToBytes(fiscalNumber);
			
			for (int i = 0; i < fiscalNumberBytes.length; i++) {
				info[i+4] = fiscalNumberBytes[i];
			}
			
			for (int i = 0; i < template.length; i++) {
				info[i+8] = template[i];
			}
			
			qrCodeImage = qrCodeHandler.encode(info);
			
			getFrame().reportGeneratedQRCode(qrCodeImage);
		};
		
		public void exitState() {
			qrCodeHandler = null;
		};
	};

	
	@Override
	public State[] getAllStates() {
		return new State[]{AUTENTICANDO_FISCAL, AGUARDANDO_CANDIDATO, COLETANDO_NUMERO_INSCRICAO, COLETANDO_DIGITAIS, IMPRIMINDO_ETIQUETA};
	}
	
	public ColetaController() {
		mainFrame = new ColetaMainFrame(this);
	}
	
	protected ColetaMainFrame getFrame() {
		return ((ColetaMainFrame)mainFrame);
	}

	@Override
	public void start() {
		super.start();
		changeState(null, AUTENTICANDO_FISCAL);
	}
	
	private void resetAllVariables() {
		
		// Apagando cartão
		getFrame().clearCardNumber();
		cardNumber = null;
		
		// Apagando template digital
		getFrame().clearDigital();
		template = null;
		
	}
	

	public void prossegueParaColetas() {
		changeState(AGUARDANDO_CANDIDATO, COLETANDO_NUMERO_INSCRICAO);
	}
	

	public void leCodigoDoCartao() {

		long startTime = System.currentTimeMillis();
		
		getFrame().clearCardNumber();
		cardNumber = null;
		
		int nTentativas = 0;
		
		// Set que guarda as ocorrências de cada número
		TreeSet<IntegerOccourrence> ordenados = new TreeSet<IntegerOccourrence>();
		Map<Integer, IntegerOccourrence> intMap = new HashMap<Integer, IntegerOccourrence>();
		
		while (true) {
			
			int deltaMaisAchado = 0;
			
			if (ordenados.size() == 1) {
				deltaMaisAchado = ordenados.last().occourrences;
			} else if (ordenados.size() > 1) {
				IntegerOccourrence last = ordenados.pollLast();
				deltaMaisAchado = last.occourrences - ordenados.last().occourrences;
				ordenados.add(last);
			}
			
			if (System.currentTimeMillis() - startTime >= SEGUNDOS_TENTANDO_LER_CARTAO * 1000 || deltaMaisAchado >= THRESHOLD_OCORRENCIAS) break;
			
			try {
				File file = ImageDiskManipulator.saveTemporaryImage(imageCapturing.getLastCapturedImage());
				nTentativas++;
				cardNumber = imageProcessor.readNumber(file.getName());
				IntegerOccourrence occ = intMap.get(cardNumber);
				if (occ == null) {
					occ = new IntegerOccourrence(cardNumber);
					intMap.put(cardNumber, occ);
				} else {
					occ.occourrences++;
				}
				ordenados.add(occ);
			} catch (UnredableNumberException e) {
				logger.warn(e.getMessage());
				try {
					Thread.sleep(10);
				} catch (InterruptedException e1) {
				}
			}
		}
		
		logger.debug("Numero de tentativas: " + nTentativas + ", sucesso: " + (cardNumber != null));
		if (ordenados.size() > 0) {
			getFrame().reportReadCardNumber(ordenados.last().number, 7);
		} else {
			getFrame().reportReadCardNumberError("Erro ao ler a imagem com número da inscrição.");
		}
		
	}
	
	private byte[] template;

	public void colherDigitais() {
		
		changeState(COLETANDO_NUMERO_INSCRICAO, COLETANDO_DIGITAIS);
		
		fingerprintHandler.start();
		fingerprintHandler.startEnroll();
	}

	public void imprimeEtiqueta() {
		
		IPrinter printer = SimpleJavaPrinter.getInstance();
		
		printer.print(this.qrCodeImage);
		
		changeState(IMPRIMINDO_ETIQUETA, AGUARDANDO_CANDIDATO);
	}


}
